package controller;

import java.util.HashMap;
import java.util.HashSet;

/**
 * Provides functionality similar to Cocoa's NSNotificationCenter, a flavor of the observer pattern.
 * Observers need to implement NotificationListener.
 * Observers register with a singleton instance of this class to be interested in
 * Notifications of specific names.
 * Each time a notification is fired, all observers will be notified via invocation
 * of the receiveNotification method.
 * See Eric M. Buck, Donald A. Yacktman: Cocoa Design Patterns
 * @author nils
 *
 */
public class NotificationCenter {
	private static NotificationCenter nc = new NotificationCenter();
	
	HashMap<String, HashSet<NotificationObserver>> observers = new HashMap<String,HashSet<NotificationObserver>>();
	
	private NotificationCenter () {
		// just hiding the public constructor.
	}
	
	
	/**
	 * Use this method to get access to the default NotificationCenter singleton instance.
	 * @return
	 */
	public static NotificationCenter defaultNotificationCenter() {
		return nc;
	}
	
	/**
	 * Register an object conforming to the NotificationObserver interface as an observer
	 * for Notifications specified by the notificationName string. Observers may register
	 * multiple notificationNames.
	 * @param notificationName
	 * @param observer
	 */
	public void addObserver(String notificationName, NotificationObserver observer) {
		synchronized (observers) {
			HashSet<NotificationObserver> observersForNotification = getObserversForNotificationNamed(notificationName);
			if (observersForNotification == null) {
				observersForNotification = new HashSet<NotificationObserver>();
				observers.put(notificationName, observersForNotification);
			}
			observersForNotification.add(observer);
		}
	}
	
	/**
	 * Removes an observer from the NotificationCenter if present.
	 * @param notificationName
	 * @param observer
	 */
	public void removeObserver(String notificationName, NotificationObserver observer) {
		synchronized (observers) {
			HashSet<NotificationObserver> oserversForNotification = getObserversForNotificationNamed(notificationName);
			if (oserversForNotification != null) {
				oserversForNotification.remove(observer);
				if (oserversForNotification.size() == 0) {
					observers.remove(notificationName);
				}
			}
		}
	}
	
	/**
	 * Sends a Notification to all observers registered for this notificationName.
	 * @param notificationName
	 * @param sender
	 */
	public void postNotification(String notificationName, Object sender) {
		postNotification(notificationName, sender, null);
	}
	
	/**
	 * Sends a Notification to all observers registered for this notificationName.
	 * @param notificationName
	 * @param sender
	 * @param userInfo
	 */
	public void postNotification(String notificationName, Object sender, HashMap<String, Object> userInfo) {
		Notification notification = new Notification(notificationName, sender, userInfo);
		postNotification(notification);
	}
	
	/**
	 * Sends a Notification to all observers registered for this notificationName.
	 * @param notification
	 */
	public void postNotification(Notification notification) {
		synchronized (observers) {
			HashSet<NotificationObserver> observersForNotification = getObserversForNotificationNamed(notification.name);
			if (observersForNotification != null) {
				for (NotificationObserver listener : observersForNotification) {
					listener.receiveNotification(notification);
				}
			}
		}
	}
	
	private HashSet<NotificationObserver> getObserversForNotificationNamed(String notificationName) {
		for (String key : observers.keySet()) {
			if (key.equals(notificationName)) {
				return observers.get(key);
			}
		}
		return null;
	}
	
	
}
